package com.example.demoweb.framework.mybatis.common.interceptor;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import javax.xml.bind.PropertyException;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
//type指向要拦截的对象的class,method指向要拦截的方法
@Intercepts({@org.apache.ibatis.plugin.Signature(type=org.apache.ibatis.executor.statement.StatementHandler.class, method="prepare", args={Connection.class, java.lang.Integer.class})})
public class PaginationPlugin
  implements Interceptor
{
  private static String dialect = null;
  private static String pageSqlId = "";

  public Object intercept(Invocation ivk) throws Throwable
  {
	//拦截StatementHandler中的prepare方式时,第一次访问的是RoutingStatementHandler,然后通过其中的delegate属性,再去访问BaseStatementHandler
    if ((ivk.getTarget() instanceof RoutingStatementHandler)) {
    	
      RoutingStatementHandler statementHandler = (RoutingStatementHandler)ivk.getTarget();
      BaseStatementHandler delegate = (BaseStatementHandler)Reflections.getValueByFieldName(statementHandler, "delegate");
      MappedStatement mappedStatement = (MappedStatement)Reflections.getValueByFieldName(delegate, "mappedStatement");
      //mappedStatement.getId()为获取配置文件中sql语句的id,如果匹配我们自定义的想要拦截的sql的id,则进行拦截,否则放行
      if (mappedStatement.getId().matches(pageSqlId)) {
        BoundSql boundSql = delegate.getBoundSql();
        //获取原配置文件中传给sql语句的参数
        Object parameterObject = boundSql.getParameterObject();
        if (parameterObject == null) {
        	//如果参数为空,则没有要求进行分页,则被拦截的方法继续执行(ivk.proceed()表示让被拦截的方法继续执行)
          return ivk.proceed();
        }
        Pagination pageView = null;
        //instanceof通过返回一个布尔值来指出，这个对象是否是这个特定类或者是它的子类的一个实例。
        //判断获取的参数对象是否是分页对象
        if ((parameterObject instanceof Pagination)) {
          pageView = (Pagination)parameterObject;
          //如果不是分页对象,则判断是否是map对象,如果是map对象,则遍历此map对象,再判断此map中是否有分页对象
        } else if ((parameterObject instanceof Map)) {
        	Map<?,?> objMap = (Map)parameterObject;
        	//遍历map中的映射关系
          for (Map.Entry entry : (objMap.entrySet()))
            if ((entry.getValue() instanceof Pagination)) {
              pageView = (Pagination)entry.getValue();
              break;
            }
        }
        //如果参数本身不是分页对象,且map中也无参数对象,则直接进行获取分页数据(感觉这里应该是以防万一,做最后的尝试)
        else {
          pageView = (Pagination)Reflections.getValueByFieldType(parameterObject, Pagination.class);
          if (pageView == null) {
            return ivk.proceed();
          }
        }
        if (pageView == null) {
          return ivk.proceed();
        }
        //获取原sql语句
        String sql = boundSql.getSql();
        Connection connection = (Connection)ivk.getArgs()[0];
        setPageParameter(sql, connection, mappedStatement, boundSql, parameterObject, pageView);
        String pageSql = generatePagesSql(sql, pageView);
        //将 boundSql 对象中的 属性sql 的值设置为 pageSql
        Reflections.setValueByFieldName(boundSql, "sql", pageSql);
      }
    }

    return ivk.proceed();
  }

  private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject, Pagination<?> pageView) throws SQLException {
    PreparedStatement countStmt = null;
    ResultSet rs = null;
    try {
      StringBuilder countSql = new StringBuilder();
      countSql.append("select count(1) from ( ");
      countSql.append(sql);
      countSql.append(" ) __tmp_count__");
      countStmt = connection.prepareStatement(countSql.toString());
      Reflections.setValueByFieldName(boundSql, "sql", countSql);
      //为sql语句注入参数
      DefaultParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
      parameterHandler.setParameters(countStmt);
      rs = countStmt.executeQuery();
      int count = 0;
      if (rs.next()) {
        count = ((Number)rs.getObject(1)).intValue();
      }
      pageView.setRowCount(count);
    } finally {
      try {
        rs.close();
      } catch (Exception localException2) {
      }
      try {
        countStmt.close();
      }
      catch (Exception localException3) {
      }
    }
  }

  private String generatePagesSql(String sql, Pagination<?> page) {
    if (page != null) {
      if ("mysql".equals(dialect.trim().toLowerCase())) {
        return buildMysqlPageSql(sql, page).toString();
      }
      if ("oracle".equals(dialect.trim().toLowerCase())) {
        return buildOraclePageSql(sql, page).toString();
      }
      if ("sqlserver".equals(dialect.trim().toLowerCase())) {
        return buildSqlserverPageSql(sql, page).toString();
      }
    }
    return sql;
  }

  public Object plugin(Object target)
  {
    return Plugin.wrap(target, this);
  }

  public void setProperties(Properties p) {
    dialect = p.getProperty("dialect");
    if (StringUtils.isBlank(dialect)) {
      try {
        throw new PropertyException("dialectName or dialect property is not found!");
      } catch (PropertyException e) {
        e.printStackTrace();
      }
    }
    pageSqlId = p.getProperty("pageSqlId");
    if (StringUtils.isBlank(pageSqlId))
      try {
        throw new PropertyException("pageSqlId property is not found!");
      } catch (PropertyException e) {
        e.printStackTrace();
      }
  }

  private StringBuilder buildOraclePageSql(String sql, Pagination<?> page)
  {
    StringBuilder pageSql = new StringBuilder();
    String beginrow = String.valueOf((page.getPageIndex() - 1) * page.getPageSize());
    String endrow = String.valueOf(page.getPageIndex() * page.getPageSize());

    pageSql.append("select * from ( select temp.*, rownum fung_row_id from ( ");
    pageSql.append(sql);
    pageSql.append(" ) temp where rownum <= ").append(endrow);
    pageSql.append(") where fung_row_id > ").append(beginrow);
    return pageSql;
  }
  //拼接mysql带分页查询的sql语句
  private StringBuilder buildMysqlPageSql(String sql, Pagination<?> page)
  {
    StringBuilder pageSql = new StringBuilder();
    String beginrow = String.valueOf((page.getPageIndex() - 1) * page.getPageSize());
    pageSql.append(sql);
    pageSql.append(new StringBuilder().append(" limit ").append(beginrow).append(",").append(page.getPageSize()).toString());
    return pageSql;
  }

  private StringBuilder buildSqlserverPageSql(String sql, Pagination<?> page)
  {
    String beginrow = String.valueOf((page.getPageIndex() - 1) * page.getPageSize() + 1);
    String endrow = String.valueOf(page.getPageIndex() * page.getPageSize());

    StringBuilder pageSql = new StringBuilder();
    String orderby = getSqlserverOrderBy(sql);
    if (StringUtils.isEmpty(orderby)) {
      orderby = "ORDER BY CURRENT_TIMESTAMP";
    }
    String distinctStr = "";
    String loweredString = sql.toLowerCase();
    if (loweredString.trim().startsWith("select")) {
      int index = 6;
      if (loweredString.startsWith("select distinct")) {
        distinctStr = "DISTINCT ";
        index = 15;
      }
      sql = sql.substring(index);
    }
    pageSql.append(sql);
    StringBuilder result = new StringBuilder();
    result.append("WITH __query_str__ AS (SELECT ")
      .append(distinctStr)
      .append("TOP 100 PERCENT ")
      .append(" ROW_NUMBER() OVER (")
      .append(orderby)
      .append(") as __row_number__, ")
      .append(pageSql)
      .append(") SELECT * FROM __query_str__ WHERE __row_number__ BETWEEN ")
      .append(beginrow)
      .append(" AND ").append(endrow)
      .append(" ORDER BY __row_number__");

    return result;
  }

  private String getSqlserverOrderBy(String sql)
  {
    String loweredString = sql.toLowerCase();
    int orderByIndex = loweredString.indexOf("order by");
    if (orderByIndex != -1) {
      return sql.substring(orderByIndex);
    }
    return "";
  }
}